package com.unnison.framework.rebind.guitview;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.uibinder.rebind.UiBinderGenerator;

import com.unnison.framework.client.ViewTemplate;
import com.unnison.framework.client.binder.Provided;
import com.unnison.framework.client.binder.ViewField;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXParseException;

import java.net.URL;
import java.util.HashSet;
import java.util.Set;

public class GuitViewHelper {
    static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder";

    private static void findUiBundleFields(Set<String> uiFields, NodeList childNodes, String binderPrefix, TreeLogger logger,
        boolean onlyNames, HashSet<String> providedFields) throws UnableToCompleteException {
        for (int n = 0; n < childNodes.getLength(); n++) {
            Node item = childNodes.item(n);
            if (item.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) item;

                String field = element.getAttribute("field");
                String namespace = element.getAttribute("type");

                String name = item.getNodeName();
                if (name.startsWith(binderPrefix)) {
                    name = name.substring(binderPrefix.length() + 1);
                    if (name.equals("style")) {
                        if (field.isEmpty()) {
                            field = "style";
                        }

                        if (namespace.isEmpty()) {
                            namespace = CssResource.class.getCanonicalName();
                        }
                    } else if (name.equals("image")) {
                        namespace = ImageResource.class.getCanonicalName();
                    } else if (name.equals("data")) {
                        namespace = DataResource.class.getCanonicalName();
                    }
                    if (!onlyNames) {
                        uiFields.add("@UiField" + (providedFields.contains(field) ? "(provided=true) " : " ") + namespace + " " + field + ";");
                    } else {
                        uiFields.add(field);
                    }
                }
            }
        }
    }

    public static Set<String> findUiFields(JClassType baseClass, TreeLogger logger, boolean onlyNames) throws UnableToCompleteException {
        Set<String> list = new HashSet<String>();
        Element documentElement = getW3cDoc(baseClass, logger).getDocumentElement();
        
        // Finds all provided fields
        HashSet<String> providedFields = getProvidedFields(baseClass, logger);
        
        String binderPrefix = documentElement.lookupPrefix(BINDER_URI);
        String uiFieldAttribute = binderPrefix + ":field";
        findUiFields(list, documentElement, uiFieldAttribute, logger, onlyNames, providedFields);
        findUiBundleFields(list, documentElement.getChildNodes(), binderPrefix, logger, onlyNames, providedFields);
        return list;
    }

    public static HashSet<String> getProvidedFields(JClassType baseClass, TreeLogger logger) throws UnableToCompleteException {
        HashSet<String> providedFields = new HashSet<String>();
        JField[] fields = baseClass.getFields();
        for (JField f : fields) {
            if (f.isAnnotationPresent(ViewField.class)) {
                String name = f.getAnnotation(ViewField.class).value();
                if (name.isEmpty()) {
                    name = f.getName();
                }
                
                if (f.isAnnotationPresent(Provided.class)) {
                    if (providedFields.contains(name)) {
                        logger.log(Type.ERROR, "There can be only one provided @ViewField for a field. Found: " + baseClass.getQualifiedSourceName() + "." + f.getName());
                        throw new UnableToCompleteException();
                    }
                    
                    providedFields.add(name);
                }
            }
        }
        return providedFields;
    }
    
    private static void findUiFields(Set<String> uiFields, Element node, String uiFieldAttribute, TreeLogger logger, boolean onlyNames, HashSet<String> providedFields)
        throws UnableToCompleteException {
        String field = node.getAttribute(uiFieldAttribute);
        if (!onlyNames) {
            if (node.hasAttribute(uiFieldAttribute)) {
                String prefix = node.getPrefix();
                String namespace = node.lookupNamespaceURI(prefix);
                String name;
                if (namespace != null) {
                    // Widgets
                    if (namespace.startsWith("urn:import:")) {
                        namespace = namespace.substring(11);
                    } else {
                        logger.log(TreeLogger.ERROR, String.format("Bad namespace. Found: %s", node.toString()));
                        throw new UnableToCompleteException();
                    }
                    name = node.getNodeName().substring(prefix.length() + 1);
                } else {
                    // Html elements
                    name = "Element";
                    namespace = "com.google.gwt.dom.client";
                }
                uiFields.add("@UiField" + (providedFields.contains(field) ? "(provided=true) " : " ") + namespace + "." + name + " " + field + ";");
            }
        } else {
            if (node.hasAttribute(uiFieldAttribute)) {
                uiFields.add(field);
            }
        }

        NodeList children = node.getChildNodes();
        for (int n = 0; n < children.getLength(); n++) {
            Node item = children.item(n);
            if (item.getNodeType() == Node.ELEMENT_NODE) {
                findUiFields(uiFields, (Element) item, uiFieldAttribute, logger, onlyNames, providedFields);
            }
        }
    }

    private static Document getW3cDoc(JClassType baseClass, TreeLogger logger) throws UnableToCompleteException {
        String templateFile = baseClass.getSimpleSourceName() + ".ui.xml";
        if (baseClass.isAnnotationPresent(ViewTemplate.class)) {
            templateFile = baseClass.getAnnotation(ViewTemplate.class).value();
        }
        String templatePath = baseClass.getPackage().getName().replace(".", "/") + "/" + templateFile;
        URL url = UiBinderGenerator.class.getClassLoader().getResource(templatePath);
        if (null == url) {
            logger.log(TreeLogger.ERROR, "Unable to find resource: " + templatePath);
            throw new UnableToCompleteException();
        }

        Document doc = null;
        try {
            doc = new W3cDomHelper(logger).documentFor(url);
        } catch (SAXParseException e) {
            logger.log(TreeLogger.ERROR, String.format("Error parsing XML (line " + e.getLineNumber() + "): " + e.getMessage(), e));
            throw new UnableToCompleteException();
        }
        return doc;
    }
}
